﻿using FFmpeg.AutoGen;
using Microsoft.Graphics.Canvas;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace FFmpegWinui3.UlitFolder
{
    public sealed unsafe class VideoStreamDecoder : IDisposable
    {
        private readonly AVCodecContext* codecContext;
        private readonly AVFormatContext* format;
        private readonly AVFrame* pFrame;
        private readonly AVPacket* packet;
        private readonly AVFrame* receivedFrame;
        private readonly int videoIndex;
        private readonly AVStream* stream;
        public VideoFrameConverter frameConvert;

        public string CodecName { get; }
        public Size FrameSize { get; }
        public AVPixelFormat PixelFormat { get; }

        /// <summary>
        /// 视频时长
        /// </summary>
        public double Duration { get; }
        public double CurrentTime { get; private set; }
        private int frameIndex = 0;
        //帧率
        public double FrameRate { get; private set; } = 0;
        public double Rate { get; private set; } = 0;


        /// <summary>
        /// 解码视频流
        /// </summary>
        /// <param name="url">文件路径</param>
        /// <param name="HWDeviceType">硬件解码器</param>
        public VideoStreamDecoder(string url, AVHWDeviceType HWDeviceType = AVHWDeviceType.AV_HWDEVICE_TYPE_NONE)
        {
            //创建容器上下文
            format = ffmpeg.avformat_alloc_context();
            //创建 帧
            receivedFrame = ffmpeg.av_frame_alloc();
            //
            var pFormatContext = format;
            //打开视频
            ffmpeg.avformat_open_input(&pFormatContext, url, null, null);
            //获取流信息
            ffmpeg.avformat_find_stream_info(format, null);
            //解码器
            AVCodec* codec = null;
            //获取视频流索引
            videoIndex = ffmpeg.av_find_best_stream(format, AVMediaType.AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0)
                ;
            //创建解码器上下文
            codecContext = ffmpeg.avcodec_alloc_context3(codec);
            //
            if (HWDeviceType != AVHWDeviceType.AV_HWDEVICE_TYPE_NONE)
                //创建硬件解码器
                ffmpeg.av_hwdevice_ctx_create(&codecContext->hw_device_ctx, HWDeviceType, null, null, 0);
            stream = format->streams[videoIndex];
            //解码器参数
            ffmpeg.avcodec_parameters_to_context(codecContext, stream->codecpar);
            //打开解码器
            ffmpeg.avcodec_open2(codecContext, codec, null);
            //解码器类型
            CodecName = ffmpeg.avcodec_get_name(codec->id);
            //视频帧大小
            FrameSize = new Size(codecContext->width, codecContext->height);
            //视频格式
            PixelFormat = codecContext->pix_fmt;
            packet = ffmpeg.av_packet_alloc();
            pFrame = ffmpeg.av_frame_alloc();
            var sourcePixelFormat = HWDeviceType == AVHWDeviceType.AV_HWDEVICE_TYPE_NONE ? PixelFormat : FFmpegHelper.GetHWPixelFormat(HWDeviceType);
            frameConvert = new VideoFrameConverter(FrameSize, sourcePixelFormat, FrameSize, AVPixelFormat.AV_PIX_FMT_BGR0);
            var v = format->start_time;
            var time_base = stream->time_base;
            //输入视频的时长（以微秒为单位）  
            Duration = format->duration;
            Duration = format->duration * 0.01 * 0.01 * 0.01;
            CurrentTime = 0;
            //帧率
            FrameRate = ffmpeg.av_q2d(stream->r_frame_rate);
            Rate = ffmpeg.av_q2d(stream->time_base);
            frameIndex = 0;
        }





        public bool TryDecodeNextFrame(out AVFrame frame)
        {
            ffmpeg.av_frame_unref(pFrame);
            ffmpeg.av_frame_unref(receivedFrame);
            int error;

            do
            {
                try
                {
                    do
                    {
                        ffmpeg.av_packet_unref(packet);
                        error = ffmpeg.av_read_frame(format, packet);
                        if (error == ffmpeg.AVERROR_EOF)
                        {
                            frame = *pFrame;
                            return false;
                        }
                    } while (packet->stream_index != videoIndex);
                    ffmpeg.avcodec_send_packet(codecContext, packet);
                }
                finally
                {
                    ffmpeg.av_packet_unref(packet);
                }

                error = ffmpeg.avcodec_receive_frame(codecContext, pFrame);
            } while (error == ffmpeg.AVERROR(ffmpeg.EAGAIN));

            if (codecContext->hw_device_ctx != null)
            {
                ffmpeg.av_hwframe_transfer_data(receivedFrame, pFrame, 0);
                frame = *receivedFrame;
            }
            else
                frame = *pFrame;

            frameIndex++;
            //计算当前播放到的秒数
            CurrentTime = pFrame->pkt_dts / (pFrame->pkt_duration * FrameRate);


            return true;
        }

        public bool GetNextFrameBitmap(out CanvasBitmap bitmap)
        {
            bitmap = null;
            if (TryDecodeNextFrame(out var frame))
            {
                var result = frameConvert.Convert(frame);
                byte[] bytes = new byte[FrameSize.Width * FrameSize.Height * 4];

                Marshal.Copy((IntPtr)result.data[0], bytes, 0, bytes.Length);
                bitmap = CanvasBitmap.CreateFromBytes(CanvasDevice.GetSharedDevice(), bytes, FrameSize.Width, FrameSize.Height, Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized);
                return true;
            }
            return false;
        }

        public CanvasBitmap AVFrameConvertBitmap(AVFrame frame)
        {
            var result = frameConvert.Convert(frame);
            byte[] bytes = new byte[FrameSize.Width * FrameSize.Height * 4];
            Marshal.Copy((IntPtr)result.data[0], bytes, 0, bytes.Length);
            var bitmap = CanvasBitmap.CreateFromBytes(CanvasDevice.GetSharedDevice(), bytes, FrameSize.Width, FrameSize.Height, Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized);
            return bitmap;
        }

        public bool SetSeek(int seekTime)
        {
            var timestamp = seekTime / Rate;
            ffmpeg.av_seek_frame(format, videoIndex, (long)timestamp, ffmpeg.AVSEEK_FLAG_BACKWARD | ffmpeg.AVSEEK_FLAG_FRAME);
            ffmpeg.av_frame_unref(pFrame);
            ffmpeg.av_frame_unref(receivedFrame);
            ffmpeg.av_packet_unref(packet);
            int error = 0;
            while (packet->pts < timestamp)
            {
                do
                {
                    do
                    {
                        ffmpeg.av_packet_unref(packet);
                        error = ffmpeg.av_read_frame(format, packet);
                        if (error == ffmpeg.AVERROR_EOF)
                        {
                            return false;
                        }
                    } while (packet->stream_index != videoIndex);

                    ffmpeg.avcodec_send_packet(codecContext, packet);
                    error = ffmpeg.avcodec_receive_frame(codecContext, pFrame);
                } while (error == ffmpeg.AVERROR(ffmpeg.EAGAIN));
            }
            frameIndex++;
            //计算当前播放到的秒数
            CurrentTime = pFrame->pkt_dts / (pFrame->pkt_duration * FrameRate);
            return true;
        }

        /// <summary>
        /// 获取上下文信息
        /// </summary>
        /// <returns></returns>
        public IReadOnlyDictionary<string, string> GetContextInfo()
        {
            AVDictionaryEntry* tag = null;
            var result = new Dictionary<string, string>();

            while ((tag = ffmpeg.av_dict_get(format->metadata, "", tag, ffmpeg.AV_DICT_IGNORE_SUFFIX)) != null)
            {
                var key = Marshal.PtrToStringAnsi((IntPtr)tag->key);
                var value = Marshal.PtrToStringAnsi((IntPtr)tag->value);
                result.Add(key, value);
            }

            return result;
        }

        /// <summary>
        /// 释放
        /// </summary>
        public void Dispose()
        {
            var frame = pFrame;
            ffmpeg.av_frame_free(&frame);

            var pPacket = packet;
            ffmpeg.av_packet_free(&pPacket);

            ffmpeg.avcodec_close(codecContext);
            var pFormatContext = format;
            ffmpeg.avformat_close_input(&pFormatContext);
        }
    }


}
